package io.gitee.twoke.generator.engine.impl.generate.method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

public class GlobalMethodManagerImpl implements GlobalMethodManager, MethodInterceptor {
    private Map<Class<?>, Object> methods = new HashMap<>();
    private Enhancer enhancer = new Enhancer();

    public static GlobalMethodManagerImpl newInstance() {
        return new GlobalMethodManagerImpl();
    }

    @Override
    public Object proxy() {
        if (methods.size() == 0) throw new IllegalArgumentException("methods is empty");
        enhancer.setInterfaces(methods.keySet().toArray(new Class[methods.size()]));
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public <T> void register(Class<T> classType,T globalMethods) {
        if (!GlobalMethod.class.isAssignableFrom(classType)) throw new IllegalArgumentException("class type is not GlobalMethod");
        if (classType == null) throw new IllegalArgumentException("classType is null");
        if (globalMethods == null) throw new IllegalArgumentException("globalMethod instance is null");
        if (!methods.containsKey(classType)) {
            methods.put(classType, globalMethods);
        }
    }

    @Override
    public <T> void register(T instance) {
        if (instance == null) throw new IllegalArgumentException("globalMethod instance is null");
        if (!GlobalMethod.class.isAssignableFrom(instance.getClass())) throw new IllegalArgumentException("class type is not GlobalMethod");
        Class<?>[] interfaces = instance.getClass().getInterfaces();
        Arrays.stream(interfaces).filter(inter -> GlobalMethod.class.isAssignableFrom(inter)).forEach(inter -> {
            register((Class<? super Object>) inter, (Object) instance);
        });
    }

    @Override
    public <T> T unregister(Class<T> clazz) {
        if (methods.containsKey(clazz)) {
            return (T) methods.remove(clazz);
        }
        return null;
    }

    @Override
    public <T> Optional<T> getOptional(Class<T> clazz) {
        return (Optional<T>) Optional.ofNullable(methods.get(clazz));
    }

    @Override
    public <T> T get(Class<T> clazz) {
        return getOptional(clazz).orElse(null);
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        if (methods.containsKey(method.getDeclaringClass())){
            return method.invoke(methods.get(method.getDeclaringClass()), args);
        }
        return null;
    }
}